package com.young.consul.health;

import com.ecwid.consul.v1.ConsulClient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.Period;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.consul.ConsulEndpoint;
import org.springframework.cloud.consul.discovery.ConsulDiscoveryClient;
import org.springframework.cloud.consul.discovery.HeartbeatProperties;
import org.springframework.cloud.consul.discovery.TtlScheduler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;

/**
 * TTL覆写
 *
 * @author ：<a href="mailto:youngkun2016@163.com">young</a>
 * @date ：Created in 2020/12/18
 */
@Configuration
@AutoConfigureAfter(ConsulClient.class)
@AutoConfigureBefore(ConsulEndpoint.class)
public class TtlSchedulerConfig {

    @Value("${check.threshold:10000}")
    private long threshold;

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty("spring.cloud.consul.discovery.heartbeat.enabled")
    public TtlScheduler ttlScheduler(ConsulClient consulClient,
                                     HeartbeatProperties heartbeatProperties) {
        return new TtlSchedulerOver(heartbeatProperties, consulClient, this.threshold);
    }

    public class TtlSchedulerOver extends TtlScheduler {
        private final Log log = LogFactory.getLog(ConsulDiscoveryClient.class);

        private final TaskScheduler scheduler = new ConcurrentTaskScheduler(
                Executors.newSingleThreadScheduledExecutor());

        private HeartbeatProperties configuration;

        private ConsulClient client;

        private long threshold;

        private final Map<String, ScheduledFuture> serviceHeartbeats = new ConcurrentHashMap<>();

        public TtlSchedulerOver(HeartbeatProperties configuration, ConsulClient client, long threshold) {
            super(configuration, client);
            this.configuration = configuration;
            this.client = client;
            this.threshold = threshold;
        }

        @Override
        public void add(String instanceId) {
            ScheduledFuture task = this.scheduler.scheduleAtFixedRate(
                    new ConsulHeartbeatTaskOver(instanceId), this.computeHearbeatInterval().toStandardDuration().getMillis());
            ScheduledFuture previousTask = this.serviceHeartbeats.put(instanceId, task);
            if (previousTask != null) {
                previousTask.cancel(true);
            }
        }

        private class ConsulHeartbeatTaskOver implements Runnable {

            private String checkId;

            ConsulHeartbeatTaskOver(String serviceId) {
                this.checkId = serviceId;
                if (!this.checkId.startsWith("service:")) {
                    this.checkId = "service:" + this.checkId;
                }
            }

            @Override
            public void run() {
                if (HealthCheckStatic.checkCount < TtlSchedulerOver.this.threshold) {
                    TtlSchedulerOver.this.client.agentCheckPass(this.checkId);
                } else {
                    TtlSchedulerOver.this.client.agentCheckFail(this.checkId);
                }
                log.info(String.format("checkCount:{%s}", HealthCheckStatic.checkCount++));
                if (log.isDebugEnabled()) {
                    log.debug("Sending consul heartbeat for: " + this.checkId);
                }
            }

        }

        protected Period computeHearbeatInterval() {
            // heartbeat rate at ratio * ttl, but no later than ttl -1s and, (under lesser
            // priority), no sooner than 1s from now
            double interval = this.configuration.getTtlValue() * this.configuration.getIntervalRatio();
            double max = Math.max(interval, 1);
            int ttlMinus1 = this.configuration.getTtlValue() - 1;
            double min = Math.min(ttlMinus1, max);
            Period heartbeatInterval = new Period(Math.round(1000 * min));
            log.debug("Computed heartbeatInterval: " + heartbeatInterval);
            return heartbeatInterval;
        }
    }
}
